﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.CompilerServices;
using System.Windows;
using System.ComponentModel;

namespace PDX.Notification
{
    /// <summary>
    /// The purpose of this class is to wrap a <see cref="BasicNotificationManager"/>
    /// while being aware of an element's scope so that elements are accessing the
    /// same underlying notification manager.
    /// </summary>
    public class ScopeAwareNotificationManager : INotificationManager
    {
        IRaiseDependencyPropertyChanges element;

        /// <summary>
        /// Initializes a new instance of the <see cref="ScopeAwareNotificationManager"/> class.
        /// </summary>
        /// <param name="element">The element.</param>
        public ScopeAwareNotificationManager(IRaiseDependencyPropertyChanges element)
        {
            this.element = element;
            element.DependencyPropertyChanged += new System.Windows.DependencyPropertyChangedEventHandler(element_DependencyPropertyChanged);
            UpdateScope();
        }

        void element_DependencyPropertyChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
        {
            if (e.Property == Scope.NotificationScopeProperty)
            {
                UpdateScope();
            }
        }

        void UpdateScope()
        {
            NotificationScope = GetNotificationScope();
            Wrapped = GetManagerForScope();
            Notify();
        }

        object _notificationScope;
        /// <summary>
        /// Gets or sets the notification scope.
        /// </summary>
        /// <value>
        /// The notification scope.
        /// </value>
        protected object NotificationScope
        {
            get { return _notificationScope; }
            set
            {
                var inpc = _notificationScope as INotifyPropertyChanged;
                if (inpc != null)
                {
                    inpc.PropertyChanged -= inpc_PropertyChanged;
                }

                _notificationScope = value;

                inpc = _notificationScope as INotifyPropertyChanged;
                if (inpc != null)
                {
                    inpc.PropertyChanged += inpc_PropertyChanged;
                }
            }
        }

        void inpc_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Notify();
        }

        INotificationManager _wrapped;
        /// <summary>
        /// Gets or sets the wrapped manager
        /// </summary>
        /// <value>
        /// The wrapped manager
        /// </value>
        protected INotificationManager Wrapped
        {
            get { return _wrapped; }
            set
            {
                if (_wrapped != null)
                {
                    _wrapped.NotificationRaised -= new EventHandler(wrapped_PropertyChanged);
                }
                _wrapped = value;
                if(_wrapped != null)
                {
                    _wrapped.NotificationRaised += new EventHandler(wrapped_PropertyChanged);
                }
            }
        }

        void wrapped_PropertyChanged(object sender, EventArgs e)
        {
            NotificationRaised(sender, e);
        }


        /// <summary>
        /// Gets the notification scope.
        /// </summary>
        /// <returns></returns>
        public virtual object GetNotificationScope()
        {
            object scope = null;

            var rootElement = element.Self.WalkUp().Where(el => Scope.GetNotificationScope(el) != null).FirstOrDefault();
            if (rootElement != null)
            {
                scope = Scope.GetNotificationScope(rootElement);
            }

            if (scope == null)
            {
                scope = element.Self.WalkUp(true).Last();
            }

            var fe = scope as FrameworkElement;
            if (fe != null && fe.DataContext != null)
            {
                scope = fe.DataContext;
            }

            return scope;
        }

        /// <summary>
        /// Gets the manager to wrap for the current <see cref="NotificationScope"/> scope.
        /// </summary>
        /// <returns></returns>
        protected INotificationManager GetManagerForScope()
        {
            INotificationManager retval = null;
            if (!managers.TryGetValue(NotificationScope, out retval))
            {
                retval = CreateNotificationManager();
                managers.Add(NotificationScope, retval);
            }

            return retval;
        }

        static ConditionalWeakTable<object, INotificationManager> managers
            = new ConditionalWeakTable<object, INotificationManager>();

        /// <summary>
        /// Creates the notification manager to be wrapped by this instance.
        /// </summary>
        /// <returns></returns>
        public virtual INotificationManager CreateNotificationManager()
        {
            return new BasicNotificationManager();
        }


        #region "INotificationManager implementation"

        /// <summary>
        /// Transactionally raises the <see cref="NotificationRaised"/> event.
        /// </summary>
        public void Notify()
        {
            Wrapped.Notify();
        }

        /// <summary>
        /// Begins a disposable transaction.
        /// Clients will probably want to use a "using" statement.
        /// </summary>
        /// <returns></returns>
        public Transaction BeginDisposableTransaction()
        {
            return Wrapped.BeginDisposableTransaction();
        }

        /// <summary>
        /// Gets a value indicating whether there is transaction in progress.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if there is transaction in progress; otherwise, <c>false</c>.
        /// </value>
        public bool IsTransactionInProgress
        {
            get { return Wrapped.IsTransactionInProgress; }
        }

        /// <summary>
        /// Gets the transaction number.
        /// This is incremented every transaction.
        /// </summary>
        public int TransactionNumber
        {
            get { return Wrapped.TransactionNumber; }
        }

        /// <summary>
        /// Signal to subscribers that state may have changed.
        /// For example, property values on the viewmodel.
        /// </summary>
        public event EventHandler NotificationRaised = delegate { };

        #endregion
    }
}
